/*
 * todo:
 * error logging? (info, warning, error)
 */


$.jfeC = {};

(function() {
	var editors = {};
	var plugins = {};
	var adapters = {};
	var validators = {};

	var settings = {
		adapter : 'default',
		baseURL : '/',
		autoInit : true,
		editable: {
			id: false,
			editor: 'TextInputEditor',
			language: 'en',
			validate: false,
			beforeEdit: 'empty',
			afterEdit: 'empty',
			beforeValidate: 'empty',
			afterValidate: 'empty',
			beforeSave: 'empty',
			afterSave: 'empty',
			onSaveSuccess: 'empty',
			onSaveError: 'empty',
			beforeAction: 'empty',
			afterAction: 'empty',
			whileUpload: 'empty'
			/* todo:
			 * there are 3 seperated phases that could success or fail:
			 * 1. editor start (successes if the editor could be started without errors)
			 * 2. editor stop (successes if the editor could be stopped, data validated
			 * 	  and save call can be started without errors)
			 * 3. after save (successes if the data is saved to the server)
			 * 
			 * each of this phases should get suitable callbacks
			 */
		}
	};

	$.jfeC.addEditor = function(name, editor) {
		var editor = $.extend({},jfeCEditor, editor);
		
		if(!editor.jfeCInit()) return false;
		editors[name] = editor;
		return true;
	};
	$.jfeC.addPlugin = function(name, plugin) {
		if(!plugin.init()) return false;
		plugins[name] = plugin;
		return true;
	};
	$.jfeC.addAdapter = function(name, adapter) {
		adapters[name] = adapter;
		return true;
	};
	$.jfeC.addValidator = function(name, validator) {
		validators[validator] = validator;
		return true;
	};

	$.jfeC.addCallback = function(editor, name, fn) {
		editors[editor].addCallback(name, fn);
		return true;
	};
	
	function loadConfiguration() {
		var options = $('body').jSonComments({
			'keyword' : 'jfeCSettings'
		});
		settings = $.extend(settings, options);
	}

	function init() {
		loadConfiguration();
		$.each(plugins, function() {
			$(this).init();
		});
		$.each(editors, function() {
			$(this).init();
		});
		
		initEditables();

		if (settings.autoInit) {
			function domManipHook(original, wrapper) {
				return function() {
					var result = original.apply(this, arguments);
					wrapper.apply(this, arguments);
					return result;
				}
			}

			$.fn.domManip = domManipHook($.fn.domManip,
					function(args) {
						initEditables();
					});
		}

        connector.init();
	}

	function initEditables(elements) {
		if(elements) {
			$(elements).filter('.jfeCEditable:not(.jfeCEditableInited)').each(function() {
				initEditable($(this));
			});
			return elements;
		} else {
			$('.jfeCEditable:not(.jfeCEditableInited)').each(function() {
				initEditable($(this));
			});
		}
	}
	
	function initEditable(editable) {
		if(!$(editable).hasClass('.jfeCEditableInited')) {
			$(editable).addClass('jfeCEditableInited');
			var options = $(editable).jSonComments({
				'keyword' : 'jfeCData'
			});
			$.each(options, function(key, val) {
				initEditor(val);
			});
		}
		return false;
	}
	
	function initEditor(options) {
		options = $.extend({},settings.editable, options);
		el = $('#'+options.elementId);
		editors[options.editor].jfeCBindStart(el, options);
		
		el.data('jfeCData', options);
	}
	
	var jfeCEditor = {
		jfeCInit: function() {
			if(!this.init()) return false;
			return true;
		},
		jfeCBindStart: function(el,options) {
			el.bind('jfeCStart', this.jfeCStart);
			el.bind('jfeCStop', this.jfeCStop);
			
			if(!this.bindStart(el, options)) {
				/*
				 * todo:
				 * add more default events
				 */
                var bindTarget = el;
                if(options.bindToElement) bindTarget = $('#'+options.bindToElement);
				bindTarget.click(function(e) { el.trigger('jfeCStart');});
				}
		},
		jfeCBindStop: function(el, options) {
			if(!this.bindStop(el, options)) {
				/*
				 * todo:
				 * add more default events
				 */
				this.getEditorElement(el).blur(function() { el.trigger('jfeCStop'); });
				}
		},
		jfeCStart: function(eData) {			
			var options = $(this).data('jfeCData');
			var el = $(this);
			
			if(!editors[options.editor].callbacks[options.beforeEdit](el, options, eData)) return false;
			if(!editors[options.editor].start(el, options, eData)) return false;
			editors[options.editor].jfeCBindStop(el, options);
			return true;
		},
		jfeCStop: function(e, eData) {
			var options = $(this).data('jfeCData');
			var el = $(this);

			var data = editors[options.editor].stop(el,options, eData);
			if(!data) return false;
			if(!editors[options.editor].jfeCSave(el,options, data)) return false;
			if(!editors[options.editor].callbacks[options.afterEdit](el, options, data)) return false;
			return true;
		},
		jfeCSave: function(el, options, data) {
			if(!editors[options.editor].callbacks[options.beforeSave](el, options, data)) return false;
			if(!editors[options.editor].jfeCValidate(el, options, data)) {
				editors[options.editor].callbacks[options.validationErrors](el,options, data);
				return false;
				}

			var requestData = adapters[settings.adapter][data.action](data, options, settings);
			if(!connector['send'](el, options, data, requestData)) {
				editors[options.editor].finish(el,options, data, false, false);
				return false;
				}
			return true;
		},
		jfeCValidate: function(el, options) {
			if(!editors[options.editor].callbacks[options.beforeValidate](el, options)) return false;
			if(!editors[options.editor].validate(el, options)) return false;
			/* 
			 * todo:
			 * how should we handle the default
			 * validation with validators versus
			 * custom editor validation?
			 * 
			 * todo:
			 * use validators to validate
			 */
			if(!editors[options.editor].callbacks[options.afterValidate](el, options)) return false;
			return true;
		},
		init: function() {
			return true;
		},
		bindStart: function() {
			return false;
		},
		bindStop: function() {
			return false;
		},
		start: function() {
			return true;
		},
		stop: function() {
			return true;
		},
		finish: function() {
			return true;
		},
		validate: function() {
			return true;
		},
		getEditorElement: function(el) {
			return $('#'+$(el).attr('id')+'_jfeCEditor');
		},
		callbacks: {
			'empty': function() { return true; } 
		},
		addCallback: function(name,fn) {
			this.callbacks[name] = fn;
			return true;
		}
	}
	
	var connector = {
        init: function() {
          $.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
            if(options.formDataObject) options.data = options.formDataObject;


              options.xhr = function() {
                  var xhr = jQuery.ajaxSettings.xhr();
                  xhr.onprogress = function(e) {
				    //options.whileUpload(el, options, data, e);
                  };
                  return xhr;
              }
          });
        },
		send: function(el, options, data, requestData) {
            if(!requestData.data) requestData.data = {};
            if(!requestData.formDataObject) requestData.formDataObject = false;

			$.ajax({
				cache: false,
				type: "POST",
				url: requestData.url,
                contentType: false,
				success: function(response) {
					/*
					 * todo:
					 * i'm not sure if the functions should get the response directly
					 * neither editor nor application should be dependend on the way
					 * a server respond.
					 * just don't know how the adapter function should work, it has
					 * to be very very flexible.
					 *
					 * todo:
					 * maybe the success and error methods should be part of the connector
					 * object and should only be called here. we'll know as soon as the
					 * fileupload and dndFileupload methods are ready.
					 */
					if(adapters[settings.adapter].isSuccess(response)) {
						editors[options.editor].callbacks[options.onSaveSuccess](el, options, data, response);
						editors[options.editor].finish(el,options, data, true, response);
						}
					else {
						editors[options.editor].callbacks[options.onSaveError](el, options, data, response);
						editors[options.editor].finish(el,options, data, false, false);
					}

					editors[options.editor].callbacks[options.afterSave](el, options, data, response);
				},
				error: function(response) {
					editors[options.editor].finish(el,options, data, false);
					editors[options.editor].callbacks[options.onSaveError](el, options, data, response);
				},
				formDataObject: requestData.formDataObject,
                data: requestData.data
			});
			
			return true;
		}
	}

	$.jfeC.init = init;
	$.jfeC.initEditables = initEditables;
	$.jfeC.settings = settings;
	$.jfeC.plugins = plugins;
})();